/*
 * Bullet Demo.h
 *
 * @author Andrew Dyer
 */
const int NUM_BLOCKS = 727; // +322? = 727.   was 405

class BulletDemo
{
  // OpenGL members
  shader colour_shader_;
  GLint viewport_width_;
  GLint viewport_height_;
  mat4 modelToProjection_;

  // Bullet members
  btDefaultCollisionConfiguration* config_;
  btCollisionDispatcher* dispatcher_;
  btDbvtBroadphase* broadphase_;
  btSequentialImpulseConstraintSolver* solver_;
  btDiscreteDynamicsWorld* world_;

  DemoEntity blocks_[NUM_BLOCKS];

  void setupDemo() {

    glFrontFace(GL_CCW);
    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);

    config_ = new btDefaultCollisionConfiguration();
    dispatcher_ = new btCollisionDispatcher(config_);
    broadphase_ = new btDbvtBroadphase();
    solver_ = new btSequentialImpulseConstraintSolver();
    world_ = new btDiscreteDynamicsWorld(dispatcher_, broadphase_, solver_, config_);

    // Create demo entities
    // Ground
    blocks_[0].init(0,  btVector3(0, 0, 0), btVector3(0, -0.3f, 0), btVector3(80, 0.25f, 80));

    float mass = 10;
    btVector3 inertia(1, 1, 1);

    float lowBlockHHeight = 0.25f;
    float lowBlockTHeight = lowBlockHHeight * 2;
    float lowHeight = lowBlockTHeight * 6;

    float midBlockHHeight = 0.2f;
    float midBlockTHeight = midBlockHHeight * 2;
    float midHeight = midBlockTHeight * 10;

    float topBlockHHeight = 0.15f;
    float topBlockTHeight = topBlockHHeight * 2;

    float blockWidth = 0.2f;
    float blockLength = 0.5f;
    

    int index = 1;

    for (int y = 0; y != 6; ++y) {
      for (int x = 0; x != 8; ++x) {
        for (int z = 0; z != 8; ++z) {
          if (y % 2 == 0) {
            if (z != 7) {
              blocks_[index].init(mass, inertia, btVector3(x - 1, lowBlockHHeight + y * lowBlockTHeight, z - 1), btVector3(blockWidth, lowBlockHHeight, blockLength));
              ++index;
            }
          }
          else {
            if (y == 5) {
              if (z == 0) {}
              else if (z == 7) {}
              else {
                if (x != 7) {
                  blocks_[index].init(mass, inertia, btVector3(x - 0.5f, lowBlockHHeight + y * lowBlockTHeight, z - 1.5f), btVector3(blockLength, lowBlockHHeight, blockWidth));
                  ++index;
                }
              }
            }
            else if (x != 7) {
              blocks_[index].init(mass, inertia, btVector3(x - 0.5f, lowBlockHHeight + y * lowBlockTHeight, z - 1.5f), btVector3(blockLength, lowBlockHHeight, blockWidth));
              ++index;
            }
          }
        }
      }
    }

    for (int y = 0; y != 10; ++y) {
      for (int x = 0; x != 6; ++x) {
        for (int z = 0; z != 6; ++z) {
          if (y % 2 == 0) {
            if (z != 5) {
              blocks_[index].init(mass, inertia, btVector3(x, lowHeight + midBlockHHeight + y * midBlockTHeight, z), btVector3(blockWidth, midBlockHHeight, blockLength));
              ++index;
            }
          }
          else {
            if (y == 9) {
              if (z == 0) {}
              else if (z == 5) {}
              else {
                if (x != 5) {
                  blocks_[index].init(mass, inertia, btVector3(x + 0.5f, lowHeight + midBlockHHeight + y * midBlockTHeight, z - 0.5f), btVector3(blockLength, midBlockHHeight, blockWidth));
                  ++index;
                }
              }
            }
            else if (x != 5) {
              blocks_[index].init(mass, inertia, btVector3(x + 0.5f, lowHeight + midBlockHHeight + y * midBlockTHeight, z - 0.5f), btVector3(blockLength, midBlockHHeight, blockWidth));
              ++index;
            }
          }
        }
      }
    }



    for (int y = 0; y != 10; ++y) {
      for (int x = 0; x != 4; ++x) {
        for (int z = 0; z != 4; ++z) {
          if (y % 2 == 0) {
            if (z != 3) {
              blocks_[index].init(mass, inertia, btVector3(x + 1, lowHeight + midHeight + topBlockHHeight + y * topBlockTHeight, z + 1), btVector3(0.15f, topBlockHHeight, blockLength));
              ++index;
            }
          }
          else {
            if (y == 9) {
              if (z == 0) {}
              else if (z == 3) {}
              else {
                if (x != 3) {
                  blocks_[index].init(mass, inertia, btVector3(x + 1.5f, lowHeight + midHeight + topBlockHHeight + y * topBlockTHeight, z + 0.5f), btVector3(blockLength, topBlockHHeight, 0.15f));
                  ++index;
                }
              }
            }
            else if (x != 3) {
              blocks_[index].init(mass, inertia, btVector3(x + 1.5f, lowHeight + midHeight + topBlockHHeight + y * topBlockTHeight, z + 0.5f), btVector3(blockLength, topBlockHHeight, 0.15f));
              ++index;
            }
          }
        }
      }
    }


    

    for (int i = 0; i != NUM_BLOCKS; ++i) {
      world_->addRigidBody(blocks_[i].getRigidBody());
    }

    world_->setGravity(btVector3(0, -10, 0));
  }

  void update() {
    world_->stepSimulation(1.0f/30);
  }

  void drawWorld(mat4 &modelToProjection, shader &shader) {
    
    // Ground
    blocks_[0].draw(shader, modelToProjection, vec4(0.4f, 0.4f, 0.4f, 1));

    for (int i = 1; i < NUM_BLOCKS; ++i) {
      blocks_[i].draw(shader, modelToProjection, vec4(0.9f, 0.8f, 0.6f, 1));
    }
  }

  void buildCamerMatrix() {
    // this matrix can be used to move objects around in the scene
    mat4 modelToWorld;
    modelToWorld.loadIdentity();

    // this is the transform from the camera to the world
    mat4 cameraToWorld;
    cameraToWorld.loadIdentity();
    cameraToWorld.translate(13, 8, 13);
    cameraToWorld.rotateY(45);
    cameraToWorld.rotateX(-22);

    // flip it around to transform from world to camera
    mat4 worldToCamera;
    cameraToWorld.invertQuick(worldToCamera);

    // build a projections matrix to add perspective
    mat4 cameraToProjection;
    cameraToProjection.loadIdentity();
    float n = 6, f = 50;
    cameraToProjection.frustum(-n, n, -n, n, n, f);

    // model -> world -> camera -> projection
    modelToProjection_ = modelToWorld * worldToCamera * cameraToProjection;
  }
  
  void render() {
    update();

    glClearColor(0.85f, 0.95f, 1, 1);
    glViewport(0, 0, viewport_width_, viewport_height_);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    drawWorld(modelToProjection_, colour_shader_);

    glutSwapBuffers();
  }

  BulletDemo() {

    // set up a simple shader to render the emissve color
    colour_shader_.init(
      "attribute vec4 pos;"
      "uniform mat4 modelToProjection;"
      "void main() { gl_Position = modelToProjection * pos; }",

      "uniform vec4 emissive_color;"
      "void main() {gl_FragColor = emissive_color;}"
    );
    
    buildCamerMatrix();

    setupDemo();
  }
  
  void setViewport(int w, int h) {
    viewport_width_ = w;
    viewport_height_ = h;
  }

  void cleanup() {

    for (int i = 0; i != NUM_BLOCKS; ++i) {
      blocks_[i].cleanup();
    }

    delete world_;
    delete solver_;
    delete broadphase_;
    delete dispatcher_;
    delete config_;
  }

public:
  static BulletDemo &get()
  {
    static BulletDemo singleton;
    return singleton;
  }

  static void reshape(int w, int h) {
    get().setViewport(w, h);
  }

  static void display() {
    get().render();
  }

  static void timer(int value) {
    glutTimerFunc(30, timer, 1); glutPostRedisplay();
  }

  static void end() {
    get().cleanup();
  }
};
